from elixir import *
from topia.termextract import extract
from hashlib import sha1
import ximple 
import unicodedata
import re
import time

ALL_CHARS = (unichr(i) for i in xrange(0x110000))
CONTROL_CHARS = ''.join(map(unichr, range(0,32) + range(127,160))).replace('\t','').replace('\n','') + \
                            unichr(0xe2) + unichr(0xc2) + unichr(0xa0) + unichr(0xc3) + unichr(0x97)
CONTROL_CHARS_RE = re.compile('[%s]' % re.escape(CONTROL_CHARS))

SPL_CHARS = """')(*&^%$#@!{}][;:/?."=-<>,\\|~`"""
SPL_CHARS_RE = re.compile('[%s]' % re.escape(SPL_CHARS))

DB = 'sqlite:///time.db'
metadata.bind= DB

##############################CONSTS##################################
OWNER = 3
EDITOR = 2
VIEWER = 1

EDITOR_UP = 4
VIEWER_UP = 5

PRIVS = ['viewer', 'editor', 'owner', 'editor and up', 'viewer and up']
##############################UTILS###################################
def connect():
    setup_all()
    return True

def commit():
    session.commit()

def rollback():
    session.rollback()

def update_db():
    create_all()


##############################OBJECTS##################################

class TimeEntity(Entity):
    """
    general entity
    """
    using_options(inheritance='multi')
    
    @classmethod
    def select(cls, *args, **kwargs):
        return cls.query.filter(*args).filter_by(**kwargs).all()
    
    @classmethod
    def force_select(cls, **kwargs):
        r = cls.select(**kwargs)
        if r:
            return r
        return [cls(**kwargs)]

class Priviliged(TimeEntity):
    using_options(inheritance='multi')
    owners = ManyToMany('User', inverse='owner')
    editors = ManyToMany('User', inverse='editor')
    viewers = ManyToMany('User', inverse='viewer')

    def get_subscribers(self):
        return self.owners + self.editors + self.viewers

    def get_editors(self):
        return self.owners + self.editors

    def add_priv(self, to_whom, as_what):
        if as_what == OWNER:
            to_whom.owner.append(self)
        elif as_what == EDITOR:
            to_whom.editor.append(self)
        elif as_what == VIEWER:
            to_whom.viewer.append(self)
        else:
            return False
        return True



#####################################################################
class Post(Priviliged):
    using_options(tablename='posts', inheritance='multi')
    content = Field(UnicodeText)
    category = ManyToOne('Category')
    tags = OneToMany('Tag')
    priority = Field(Integer)
    template = ManyToOne('Template')

    def __repr__(self):
        return '<Timemaker Post in category %s of priority %d (%d tags)>'%(self.category.name, self.priority, len(self.tags))

    def rendered(self):
        """ 
        renders the content in self (Post object) and returns a RenderedPost object. 
        """
        return RenderedPost(self) 

    def can_edit(self, user):
        return user in self.owners + self.editors

    def can_see(self, user):
        return user in self.owners + self.editors + self.viewers

    def edit(self, user, form):
        perm = self.can_edit(user)
        if not perm:
            return self
        self._set_post_xml(form)
        return self

    def _set_post_xml(self, form):
        priority = form['priority']
        self.priority = int(priority)
        self.content = ximple.dict2xml(form)

#####################################################################
class User(TimeEntity):
    using_options(tablename='users', inheritance='multi')
    owner = ManyToMany('Priviliged')
    editor = ManyToMany('Priviliged')
    viewer = ManyToMany('Priviliged')
    username = Field(Unicode(20), unique = True)
    pass_hash = Field(Unicode(40))
    notifications = OneToMany('Notification')
    challenges = OneToMany('Challenge')

    def __repr__(self):
        return '<Timemaker User "%s">'%(self.username)

    def add_category(self, category):
        c = Category(name = category)
        self.owner.append(c)
        t = Template.select(id = 3)[0]
        c.templates.append(Template(title = t.title, content = t.content))
        return c

    def add_project(self, project):
        c = Project(name = project)
        c.owners.append(self)
        self.owner.append(c)
        return c

    def share(self, content, with_whom, as_what):
        if (content not in self.owner and self not in content.owners) or not isinstance(content, Priviliged):
            print "you can't do that!"
            return False
        content.add_priv(with_whom, as_what)
        if isinstance(content, Category):
            txt = "A %s has been shared with you as %s by %s."%(content.__class__.__name__, PRIVS[as_what - 1], self.username)
            Notification(user = with_whom, message = txt)
            for p in content.posts:
                self.share(p, with_whom, as_what)
        return True


    @staticmethod
    def find(user):
        try:
            return User.query.filter_by(username = user).one()
        except:
            return None
    
    def get_categories(self, privilege = OWNER):
        cats = []
        if privilege == VIEWER:
            cats =  [c for c in self.viewer if isinstance(c, Category) and not isinstance(c, Project)]
        elif privilege == EDITOR:
            cats =  [c for c in self.editor if isinstance(c, Category) and not isinstance(c, Project)]
        elif privilege == OWNER:
            cats =  [c for c in self.owner if isinstance(c, Category) and not isinstance(c, Project)]
        elif privilege == EDITOR_UP:
            cats = [c for c in self.editor if isinstance(c, Category) and not isinstance(c, Project)] +\
                   [c for c in self.owner if isinstance(c, Category) and not isinstance(c, Project)]
        elif privilege == VIEWER_UP:
            cats =  [c for c in self.editor if isinstance(c, Category) and not isinstance(c, Project)] +\
                   [c for c in self.owner if isinstance(c, Category) and not isinstance(c, Project)] +\
                   [c for c in self.viewer if isinstance(c, Category) and not isinstance(c, Project)]
        return cats
    
    def get_projects(self, privilege = OWNER):
        projs = []
        if privilege == VIEWER:
            projs = [c for c in self.viewer if isinstance(c, Project)]
        elif privilege == EDITOR:
            projs = [c for c in self.editor if isinstance(c, Project)]
        elif privilege == OWNER:
            projs = [c for c in self.owner if isinstance(c, Project)]
        elif privilege == EDITOR_UP:
            projs = [c for c in self.editor if isinstance(c, Project)] +\
                   [c for c in self.owner if isinstance(c, Project)]
        elif privilege == VIEWER_UP:
            projs = [c for c in self.editor if isinstance(c, Project)] +\
                   [c for c in self.owner if isinstance(c, Project)] +\
                   [c for c in self.viewer if isinstance(c, Project)]
        return projs

    def issue_challenge(self, username, cid, deadline, target):
        u = User.find(username)
        c = Category.select(id = cid)[0]
        d = int(time.mktime(time.strptime(deadline, "%H:%M %d/%m/%Y")))
        h = Challenge(sender = self.username, user = u, category = c, date = d, target = target)
        commit()
        n = Notification(user = u)
        commit()
        cod = "<a href='javascript:accept(%d, %d)'>accept</a>&nbsp;&nbsp;<a href='javascript:decline(%d, %d)'>decline</a>"%(h.id, n.id, h.id, n.id)
        txt = "You have been challenged by %s on %s \"%s\" to reach %d points by %s.<br>%s"%(self.username, c.__class__.__name__, c.name, target, deadline, cod)
        n.message = txt
        commit()

#####################################################################
class Tag(TimeEntity):
    using_options(tablename='tags', inheritance='multi')
    tag = Field(Unicode(60))
    occurs = Field(Integer)
    post = ManyToOne('Post')

    def __repr__(self):
        return '<Timemaker Tag "%s" occurs %d times>'%(self.tag, self.occurs)


#####################################################################
class Template(TimeEntity):
    using_options(tablename='templates', inheritance='multi')
    category = ManyToOne('Category')
    content = Field(UnicodeText)
    title = Field(Unicode(20))


#####################################################################
class Category(Priviliged):
    using_options(tablename='categories', inheritance='multi')
    name = Field(Unicode(20))
    templates = OneToMany('Template')
    posts = OneToMany('Post')
    challenges = OneToMany('Challenge')

    def __repr__(self):
        return '<Timemaker Category "%s" with %d posts>'%(self.name, len(self.posts))

    def create_post(self, user, form, template_id):
        p = Post()
        self.posts.append(p)
        p.category = self
        tags = extract_tags(form['content'].lower()) # What is 'content' here?
        for t in tags:
            xt = Tag(tag = t[0], occurs = t[1])
            p.tags.append(xt)
        if not isinstance(user, User):
            user = User.find(user)
        p.owners = [user]
        p.editors = []
        p.viewers = []
        for u in self.owners:
            if u.username != user.username:
                p.add_priv(u, OWNER)
        for u in self.editors:
            if u.username != user.username:
                p.add_priv(u, EDITOR)
        for u in self.viewers:
            p.add_priv(u, VIEWER)
        p.edit(user, form) # TODO
        p.template = Template.select(id = template_id)[0]
        for s in self.get_subscribers():
            if s != user:
                txt = "%s added a new post to the %s \"%s\"."%(user.username, self.__class__.__name__, self.name)
                Notification(user = s, message = txt)
        return p

    def filter_by_tag(self, tag):
        posts = self.posts
        tposts = []
        for p in posts:
            if tag in [t.tag for t in p.tags]:
                tposts.append(p)
        return tposts

#####################################################################
class Project(Category):
    using_options(tablename='projects', inheritance='multi')

    def __repr__(self):
        return '<Timemaker Project "%s" with %d posts>'%(self.name, len(self.posts))

######################################################################
class Notification(TimeEntity):
    using_options(tablename='notifications', inheritance='multi')
    user = ManyToOne('User')
    message = Field(UnicodeText)
    seen = Field(Boolean, default = False)

    def __repr__(self):
        return '<Timemaker Notification to %s : %s>'%(self.user.username, self.message)

########################################################################
class Challenge(TimeEntity):
    using_options(tablename='challenges', inheritance='multi')
    sender = Field(Unicode(20))
    user = ManyToOne("User")
    date = Field(Integer)
    category = ManyToOne("Category")
    target = Field(Integer)
    accepted = Field(Boolean, default = False)

    @property
    def deadline(self):
        return time.strftime("%H:%M %d/%m/%Y", time.localtime(self.date))

    def __repr__(self):
        un = self.user.username
        sn = self.sender
        cop = self.category.__class__.__name__
        cn = self.category.name
        tr = self.target
        dl = self.deadline
        return "<Time Challenge for %s by %s about the %s \"%s\" (%s, %d remaining)>"%(un, sn, cop, cn, dl, tr)

    def accept(self):
        txt = "%s has accepted your challenge for %s!"%(self.user.username, self.category.name)
        Notification(user = User.find(self.sender), message = txt)
        self.accepted = True

    def decline(self):
        txt = "%s has declined your challenge for %s..."%(self.user.username, self.category.name)
        Notification(user = User.find(self.sender), message = txt)
        self.delete()

#################################UTILS##################################

class RenderedPost(object):
    def __init__(self, post):
        if not post.content:
            self.content = ''
        else:
            xobj = ximple.Ximple(str(post.content))
            for node in xobj:
                self.__setattr__(node.tag, node.text)

        self.priority = post.priority
        self.id = post.id
        self.description = self.content[:147]
        if len(self.description) == 147:
            self.description += '...'


def setup_acount(user, password):
    return User(username = user, pass_hash = sha1(password).hexdigest())

def extract_tags(post):
    post = remove_control_chars(post)
    post = remove_special_chars(post)
    extractor = extract.TermExtractor()
    extractor.filter = extract.DefaultFilter(singleStrengthMinOccur=1)
    tags = sorted(extractor(post))
    return [(x[0], x[1]) for x in tags if x[0]]

def remove_control_chars(s):
    return CONTROL_CHARS_RE.sub('', s)

def remove_special_chars(s):
    return SPL_CHARS_RE.sub('', s)